<?php
// +---------------------------------------------------------------------------
// | Mini Framework Lite
// +---------------------------------------------------------------------------
// | Copyright (c) 2021-2022 http://www.sunbloger.com
// +---------------------------------------------------------------------------
// | Licensed under the Apache License, Version 2.0 (the "License");
// | you may not use this file except in compliance with the License.
// | You may obtain a copy of the License at
// |
// | http://www.apache.org/licenses/LICENSE-2.0
// |
// | Unless required by applicable law or agreed to in writing, software
// | distributed under the License is distributed on an "AS IS" BASIS,
// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// | See the License for the specific language governing permissions and
// | limitations under the License.
// +---------------------------------------------------------------------------
// | Source: https://gitee.com/jasonwei/miniframework-lite
// +---------------------------------------------------------------------------
// | Author: Jason Wei <jasonwei06@hotmail.com>
// +---------------------------------------------------------------------------
// | Website: http://www.sunbloger.com/
// +---------------------------------------------------------------------------
namespace Mini;

// 系统目录分隔符
defined('DS') or define('DS', DIRECTORY_SEPARATOR);

// 应用路径
defined('APP_PATH') or define('APP_PATH', dirname(dirname($_SERVER['SCRIPT_FILENAME'])));

// 应用命名空间名称
defined('APP_NAMESPACE') or define('APP_NAMESPACE', 'App');

// 应用运行环境
defined('APP_ENV') or define('APP_ENV', 'prod');

// 伪静态扩展名
defined('URL_SUFFIX') or define('URL_SUFFIX', 'htm|html|shtml');

// URL（伪静态）分割符号
defined('URL_SPLIT_SYMBOL') or define('URL_SPLIT_SYMBOL', '_');

// 是否显示错误信息
defined('SHOW_ERROR') or define('SHOW_ERROR', false);

// 是否显示开发者调试信息
defined('SHOW_DEBUG') or define('SHOW_DEBUG', true);

// 配置文件路径
defined('CONFIG_PATH') or define('CONFIG_PATH', APP_PATH . DS . 'Config');

// 布局功能开关
defined('LAYOUT_ON') or define('LAYOUT_ON', false);

// 布局文件路径
defined('LAYOUT_PATH') or define('LAYOUT_PATH', APP_PATH . DS . 'Layout');

// WEB站点根目录对应的路径
defined('PUBLIC_PATH') or define('PUBLIC_PATH', dirname($_SERVER['SCRIPT_FILENAME']));

// HTTP缓存
defined('HTTP_CACHE_CONTROL') or define('HTTP_CACHE_CONTROL', 'private');

// 是否开启日志
defined('LOG_ON') or define('LOG_ON', false);

// 日志存储模式（1为文件，2为数据库）
defined('LOG_MODE') or define('LOG_MODE', 1);

// 日志记录等级
defined('LOG_LEVEL') or define('LOG_LEVEL', 'EMERG,ALERT,CRIT,ERROR,WARNING,NOTICE,INFO,DEBUG,SQL');

// 日志存储路径（LOG_MODE为1时生效）
defined('LOG_PATH') or define('LOG_PATH', APP_PATH . DS . 'Log');

// 日志存储的数据库配置（LOG_MODE为2时生效）
defined('LOG_DB_CONFIG') or define('LOG_DB_CONFIG', 'database:default');

// 日志存储的数据表名（LOG_MODE为2时生效）
defined('LOG_TABLE_NAME') or define('LOG_TABLE_NAME', 'log');

// CSRF令牌功能开关
defined('CSRF_TOKEN_ON') or define('CSRF_TOKEN_ON', false);

// CSRF令牌获得方式 cookie | header
defined('CSRF_TYPE') or define('CSRF_TYPE', 'cookie');

class App
{
    
    /**
     * 控制器
     *
     * @var string
     */
    public $controller;
    
    /**
     * 动作
     *
     * @var string
     */
    public $action;
    
    /**
     * Router实例
     *
     * @var Router
     */
    protected $_router;
    
    /**
     * Params实例
     *
     * @var Params
     */
    protected $_params;
    
    /**
     * Request实例
     *
     * @var Request
     */
    protected $_request;
    
    /**
     * App实例
     *
     * @var App
     */
    protected static $_instance;
    
    /**
     * 数据库对象池
     *
     * @var array
     */
    private $_dbPool;
    
    /**
     * 获取实例
     *
     * @return object
     */
    public static function getInstance()
    {
        Loader::getInstance();
        if (self::$_instance === null) {
            self::$_instance = new self();
        }
        return self::$_instance;
    }
    
    /**
     * 构造
     */
    final protected function __construct()
    {
        set_error_handler('Mini\App::customError');
        set_exception_handler('Mini\App::customExcepion');
        
        if (LOG_ON === true) {
            Log::getInstance();
        }
    }
    
    /**
     * 克隆
     */
    private function __clone()
    {}

    /**
     * 自定义异常处理方法
     *
     * @param \Throwable $e
     */
    public static function customExcepion(\Throwable $e)
    {
        Log::record($e->getMessage(), Log::ERROR, ['file' => $e->getFile(), 'line' => $e->getLine()]);
        self::showError([
            'level'     => 'ERROR',
            'message'   => $e->getMessage(),
            'file'      => $e->getFile(),
            'line'      => $e->getLine()
        ], true);
    }

    /**
     * 自定义错误处理方法
     *
     * @param int $level
     * @param string $message
     * @param string $file
     * @param int $line
     */
    public static function customError($level, $message, $file, $line)
    {
        $error = [
            'message' => $message,
            'file' => $file,
            'line' => $line
        ];
        
        switch ($level) {
            
            case E_ERROR:
            case E_PARSE:
            case E_CORE_ERROR:
            case E_COMPILE_ERROR:
            case E_USER_ERROR:
                Log::record($message, Log::ERROR, ['file' => $file, 'line' => $line]);
                $error['level'] = Log::ERROR;
                self::showError($error, true);
                die();
                break;
                
            case E_WARNING:
                Log::record($message, Log::WARNING, ['file' => $file, 'line' => $line]);
                $error['level'] = Log::WARNING;
                self::showError($error);
                break;
                
            default:
                Log::record($message, Log::NOTICE, ['file' => $file, 'line' => $line]);
                $error['level'] = Log::NOTICE;
                self::showError($error);
                break;
        }
    }
    
    /**
     * 开始
     */
    public function run()
    {
        $this->_params = Params::getInstance();
        $this->_request = Request::getInstance();
        $this->getRouter();
        $requestParams = $this->_request->parseRequestParams($this->_router->getRouteType());
        $isCli = $this->_router->isCli();
        unset($this->_router);
        if (! empty($requestParams)) {
            $this->_params->setParams($requestParams);
        }
        
        // Check CSRF-Token
        if (CSRF_TOKEN_ON === true) {
            if ($isCli === false) {
                $serverCsrfToken = $this->_request->loadCsrfToken('session');
                if (! $serverCsrfToken) {
                    $this->_request->createCsrfToken(CSRF_TYPE);
                } else {
                    if ($this->_request->checkCsrfToken() === true) {
                        $this->_request->createCsrfToken(CSRF_TYPE);
                    } else {
                        $http = Http::getInstance();
                        $http->sendHttpStatus(403);
                        echo 'Client CSRF-Token invalid.';
                        die();
                    }
                }
            }
        }
        
        $this->dispatch();
    }

    /**
     * 设置控制器
     * 
     * @param string $value
     * @return \Mini\App
     */
    public function setController($value)
    {
        $this->controller = $value;
        
        return $this;
    }

    /**
     * 设置动作
     * 
     * @param string $value
     * @return \Mini\App
     */
    public function setAction($value)
    {
        $this->action = strtolower($value);
        
        return $this;
    }

    /**
     * 调派
     */
    public function dispatch()
    {
        $controllerName = ucfirst($this->controller);
        $controllerFile = APP_PATH . DS . 'Controller' . DS . $controllerName . '.php';
        
        if (! file_exists($controllerFile)) {
            throw new Exception('Controller file "' . $controllerFile . '" not found.', 404);
        }
        
        $controllerName = APP_NAMESPACE . '\\Controller\\' . $controllerName;
        if (class_exists($controllerName)) {
            $controller = new $controllerName();
        } else {
            throw new Exception('Controller "' . $controllerName . '" does not exist.', 404);
        }
        
        $action = $this->action . 'Action';
        
        if (method_exists($controller, $action)) {
            $controller->$action();
        } else {
            throw new Exception('Action "' . $this->action . '" does not exist.', 404);
        }
    }
    
    /**
     * 获取路由器对象
     *
     * @return object
     */
    public function getRouter()
    {
        if ($this->_router === null) {
            $this->_router = new Router();
        }
        return $this->_router;
    }
    
    /**
     * 输出错误
     *
     * @param array $error
     * @param boolean $fatal
     */
    public static function showError($error = [], $fatal = false)
    {
        if (SHOW_ERROR === true) {
            if (! empty($error) && is_array($error)) {
                $isCli = preg_match("/cli/i", PHP_SAPI) ? true : false;
                if ($isCli) {
                    $body = "{$error['level']}: {$error['message']} in {$error['file']} on line {$error['line']}\n";
                } else {
                    $body = "<p><b>{$error['level']}</b>: {$error['message']} in <b>{$error['file']}</b> on line <b>{$error['line']}</b></p>\n";
                }
                
                echo $body;
            }
        } else {
            if ($fatal === true) {
                Exception::showErrorPage(500);
            }
        }
    }
}

class Loader
{
    
    /**
     * Loader Instance
     *
     * @var Loader
     */
    protected static $_instance;
    
    /**
     * 函数库清单数组
     *
     * @var array
     */
    private static $_funcs = [];
    
    /**
     * 获取实例
     */
    public static function getInstance()
    {
        if (self::$_instance === null) {
            self::$_instance = new self();
        }
        return self::$_instance;
    }
    
    /**
     * 构造
     */
    final protected function __construct()
    {
        spl_autoload_register([
            __CLASS__,
            'Mini\Loader::autoload'
        ]);
    }
    
    /**
     * 克隆
     */
    private function __clone()
    {}
    
    /**
     * 自动载入
     *
     * @param string $class
     */
    public function autoload($class)
    {
        try {
            $this->loadClass($class);
            return true;
        } catch (Exception $e) {
            return false;
        }
    }
    
    /**
     * 载入类
     *
     * @param string $class
     * @throws Exception
     */
    public static function loadClass($class)
    {
        $className = ltrim($class, '\\');
        $classPath = '';
        $namespace = '';
        $lastPos = strripos($className, '\\');
        
        if ($lastPos !== false) {
            $namespace = substr($className, 0, $lastPos);
            $classPath = str_replace('\\', DS, $namespace);
            $name = strstr($namespace, '\\', true);
            
            if ($name == APP_NAMESPACE) {
                $classPath = APP_PATH . ltrim($classPath, APP_NAMESPACE);
            } else {
                //$classPath = MINI_PATH . DS . str_replace($name . '\\', '', $namespace);;
            }
            
            $className = substr($className, $lastPos + 1);
        } else {
            $classPath = APP_PATH . DS . 'Model';
        }
        
        $classfile = $classPath . DS . $className . '.php';
        
        if (file_exists($classfile)) {
            include_once ($classfile);
        } else {
            throw new Exception('Library "' . $className . '" not found.');
        }
        
        return true;
    }
    
    /**
     * 加载函数库
     *
     * @param string $func
     * @throws Exception
     * @return boolean
     */
    public static function loadFunc($func)
    {
        $file = APP_PATH . DS . 'Function' . DS . ucfirst($func) . '.func.php';
        
        $key = md5($file);
        if (! isset(self::$_funcs[$key])) {
            if (file_exists($file)) {
                include ($file);
                self::$_funcs[$key] = true;
            } else {
                throw new Exception('Function "' . $func . '" not found.');
            }
        }
        
        return true;
    }
}

class Log
{
    
    /**
     * 错误等级
     *
     * @var string
     */
    const EMERG     = 'EMERG';
    const ALERT     = 'ALERT';
    const CRIT      = 'CRIT';
    const ERROR     = 'ERROR';
    const WARNING   = 'WARNING';
    const NOTICE    = 'NOTICE';
    const INFO      = 'INFO';
    const DEBUG     = 'DEBUG';
    
    /**
     * Log Instance
     *
     * @var Params
     */
    protected static $_instance;
    
    /**
     * 日志数组
     *
     * @var array
     */
    protected static $_logs = [];
    
    /**
     * 获取实例
     */
    public static function getInstance()
    {
        if (self::$_instance === null) {
            self::$_instance = new self();
            register_shutdown_function([Log::getInstance(), 'write']);
        }
        return self::$_instance;
    }
    
    /**
     * 构造
     */
    final protected function __construct()
    {}
    
    /**
     * 克隆
     */
    private function __clone()
    {}
    
    /**
     * 记录日志
     *
     * @param string $message
     * @param string $level
     * @param array $position ['file' => __FILE__, 'line' => __LINE__]
     */
    public static function record($message, $level = self::ERROR, $position = null)
    {
        if (self::checkLevel($level) === true) {
            self::$_logs[] = [
                'time' => date('Y-m-d H:i:s'),
                'level' => $level,
                'body' => $message,
                'file' => isset($position['file']) ? $position['file'] : null,
                'line' => isset($position['line']) ? $position['line'] : null
            ];
        }
    }
    
    /**
     * 写入日志
     */
    public function write()
    {
        if (LOG_MODE === 1) {
            $this->writeToFile();
        } elseif (LOG_MODE === 2) {
            $this->writeToDb();
        }
    }
    
    /**
     * 检查日志级别
     *
     * @param string $level
     * @return boolean
     */
    public static function checkLevel($level)
    {
        return in_array($level, explode(',', LOG_LEVEL));
    }
    
    /**
     * 写入日志到文件
     *
     * @throws Exception
     */
    private function writeToFile()
    {
        $c = count(self::$_logs);
        $t = isset(self::$_logs[$c - 1]['time']) ? strtotime(self::$_logs[$c - 1]['time']) : time();
        is_dir(LOG_PATH) or @mkdir(LOG_PATH, 0700, true);
        $logFile = LOG_PATH . DS . date('Y-m-d', $t) . '.log';
        foreach (self::$_logs as $log) {
            $result = file_put_contents(
                $logFile,
                "{$log['time']} - [{$log['level']}: {$log['body']}] - [F: {$log['file']}][L: {$log['line']}]" . PHP_EOL,
                FILE_APPEND | LOCK_EX
                );
            if ($result === false) {
                throw new Exception('Log write fail.');
            }
        }
    }
    
    /**
     * 写入日志到数据库
     */
    private function writeToDb()
    {
        if (! empty(self::$_logs)) {
            $dbParams = \Mini\Config::getInstance()->load(LOG_DB_CONFIG);
            $db = new \Mini\Db($dbParams);
            if ($db->checkTableIsExist(LOG_TABLE_NAME) === false) {
                $sql = "CREATE TABLE `" . LOG_TABLE_NAME . "` (
                `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
                `time` datetime DEFAULT NULL,
                `level` varchar(16) DEFAULT NULL,
                `body` text DEFAULT NULL,
                `file` varchar(255) DEFAULT NULL,
                `line` int(10) unsigned DEFAULT NULL,
                PRIMARY KEY (`id`)
            ) ENGINE=InnoDB DEFAULT CHARSET=" . $dbParams['charset'] . ";";
                $db->execSql($sql);
            }
            $db->insertAll(LOG_TABLE_NAME, self::$_logs);
        }
    }
}

class Params
{
    
    /**
     * Params Instance
     *
     * @var Params
     */
    protected static $_instance;
    
    /**
     * 参数数组
     *
     * @var array
     */
    public $_params = [];
    
    /**
     * POST数组
     *
     * @var array
     */
    public $_post = [];
    
    /**
     * GET数组
     *
     * @var array
     */
    public $_get = [];
    
    /**
     * 获取实例
     */
    public static function getInstance()
    {
        if (self::$_instance === null) {
            self::$_instance = new self();
        }
        return self::$_instance;
    }
    
    /**
     * 构造
     */
    final protected function __construct()
    {
        if (! empty($_GET)) {
            foreach ($_GET as $key => $val) {
                if (is_array($val)) {
                    $this->_get[$key] = $this->getArray($val);
                } else {
                    if (is_int($val)) {
                        $this->_get[$key] = $this->getInt($val);
                    } else {
                        $this->_get[$key] = $this->getStr($val);
                    }
                }
            }
        }
        
        if (! empty($_POST)) {
            foreach ($_POST as $key => $val) {
                if (is_array($val)) {
                    $this->_post[$key] = $this->getArray($val);
                } else {
                    if (is_int($val)) {
                        $this->_post[$key] = $this->getInt($val);
                    } else {
                        $this->_post[$key] = $this->getStr($val);
                    }
                }
            }
        }
    }
    
    /**
     * 克隆
     */
    private function __clone()
    {}
    
    /**
     * 获得一个整型变量
     *
     * @param mixed $number
     * @return int
     */
    private function getInt($number)
    {
        return intval($number);
    }
    
    /**
     * 获得一个字符型变量
     *
     * @param mixed $string
     * @return string
     */
    private function getStr($string)
    {
        if (! function_exists("get_magic_quotes_gpc") || ! get_magic_quotes_gpc()) {
            $string = addslashes($string);
        }
        return $string;
    }
    
    /**
     * 递归方式过滤数组
     *
     * @param array $array
     * @return array
     */
    private function getArray($array)
    {
        foreach ($array as $key => $val) {
            if (is_array($val)) {
                $array[$key] = $this->getArray($val);
            } else {
                if (is_int($val)) {
                    $array[$key] = $this->getInt($val);
                } else {
                    $array[$key] = $this->getStr($val);
                }
            }
        }
        return $array;
    }
    
    /**
     * checkInject 检测传入的字符串是否含有引起SQL注入的字符
     *
     * @param string $string
     * @return boolean
     */
    public function checkInject($string)
    {
        return preg_match('/select|insert|update|delete|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile/i', $string);
    }
    
    /**
     * 存入参数数组
     *
     * @param array $params
     */
    public function setParams(array $params)
    {
        if (! is_array($params) || empty($params)) {
            return false;
        }
        $this->_params = $this->_params + (array) $params;
        
        foreach ($params as $key => $value) {
            if ($value === null) {
                unset($this->_params[$key]);
            }
        }
        
        return true;
    }
    
    /**
     * 取出参数数组
     *
     * @return array:
     */
    public function getParams()
    {
        return $this->_params;
    }
    
    /**
     * 存入一个参数
     *
     * @param string $key
     * @param boolean $value
     */
    public function setParam($key, $value)
    {
        if ($key === null || $key == "") {
            return false;
        }
        $key = (string) $key;
        
        if (($value === null) && isset($this->_params[$key])) {
            unset($this->_params[$key]);
        } elseif ($value !== null) {
            $this->_params[$key] = $value;
        }
        
        return true;
    }
    
    /**
     * 取出一个参数
     *
     * @param string $key
     * @return $value
     */
    public function getParam($key)
    {
        $value = null;
        $key = (string) $key;
        if (isset($this->_params[$key])) {
            $value = $this->_params[$key];
        }
        
        return $value;
    }
    
    /**
     * 获取POST数据
     *
     * @param mixed $key
     */
    public function getPost($key = null)
    {
        $value = null;
        if ($key === null) {
            $value = $this->_post;
        } else {
            if (isset($this->_post[$key])) {
                $value = $this->_post[$key];
            }
        }
        
        return $value;
    }
    
    /**
     * 获取查询数据（GET）
     *
     * @param mixed $key
     */
    public function getQuery($key = null)
    {
        $value = null;
        if ($key === null) {
            $value = $this->_get;
        } else {
            if (isset($this->_get[$key])) {
                $value = $this->_get[$key];
            }
        }
        
        return $value;
    }
}

class Router
{
    
    /**
     * Request实例
     *
     * @var Request
     */
    private $_request;
    
    /**
     * 路由方式
     *
     * @var string
     */
    protected $_routeType;
    
    /**
     * 路径数组
     *
     * @var string
     */
    protected $_uriArray;
    
    /**
     * 命令行参数数组
     *
     * @var array
     */
    protected $_cliParamsArray = [];
    
    /**
     * 构造
     */
    public function __construct()
    {
        $this->_request = Request::getInstance();
        
        if (true === $this->isCli()) {
            // CLI (/index.php Controller/Action param1=value1 param2=value2 ...)
            $this->setRouteType('cli');
        } elseif (false === strpos($_SERVER['REQUEST_URI'], $_SERVER['SCRIPT_NAME'])) {
            // Rewrite (/Controller/Action/param1/value1/param2/value2)
            $this->setRouteType('rewrite');
        } else {
            // GET (/index.php?c=index&a=index)
            $this->setRouteType('get');
        }
        
        $this->route();
    }

    /**
     * 路由
     *
     * @throws Exception
     */
    public function route()
    {
        if ($this->_routeType == 'cli') {
            if (isset($_SERVER['argc']) && $_SERVER['argc'] > 1) {
                if (preg_match("/^([a-zA-Z][a-zA-Z0-9]*)\/([a-zA-Z][a-zA-Z0-9]*)$/", $_SERVER['argv'][1], $m)) {
                    $controller = isset($m[1]) ? $m[1] : 'index';
                    $action = isset($m[2]) ? $m[2] : 'index';
                } else {
                    throw new Exception('Request params invalid.');
                }
            } else {
                $controller = $action = 'index';
            }
        } elseif ($this->_routeType == 'rewrite') {
            $this->_uriArray = $this->parseUrlToArray();
            $controller = (isset($this->_uriArray[1]) && ! empty($this->_uriArray[1])) ? $this->_uriArray[1] : 'index';
            $action = (isset($this->_uriArray[2]) && ! empty($this->_uriArray[2])) ? $this->_uriArray[2] : 'index';
        } elseif ($this->_routeType == 'get') {
            if (empty($_SERVER['QUERY_STRING'])) {
                $controller = $action = 'index';
            } else {
                $queryStringArray = $this->_request->getQueryStringArray();
                $controller = isset($queryStringArray['c']) ? $queryStringArray['c'] : 'index';
                $action = isset($queryStringArray['a']) ? $queryStringArray['a'] : 'index';
            }
        }
        
        if ($this->checkRoute($controller)) {
            App::getInstance()->setController($controller);
        } else {
            throw new Exception('Controller name invalid.', 404);
        }
        
        if ($this->checkRoute($action)) {
            App::getInstance()->setAction($action);
        } else {
            throw new Exception('Action name invalid.', 404);
        }
    }

    /**
     * 存入路由方式
     */
    public function setRouteType($type)
    {
        if ($type == 'cli' || $type == 'rewrite' || $type == 'get') {
            $this->_routeType = $type;
        } else {
            throw new Exception('Router type invalid.', 500);
        }
        
        return $this;
    }
    
    /**
     * 读取路由方式
     *
     * @return string
     */
    public function getRouteType()
    {
        return $this->_routeType;
    }
    
    /**
     * 解析Url为数组
     *
     * @return array
     */
    private function parseUrlToArray()
    {
        $requestUri = $this->_request->getRequestUri();
        $baseUrl = $this->_request->getBaseUrl();
        if ($requestUri != $baseUrl) {
            $requestUri = str_replace($baseUrl, '', $requestUri);
        }
        $uriArray = explode('/', $requestUri);
        
        return $uriArray;
    }
    
    /**
     * 检查路由参数合法性
     *
     * @param mixed $value
     * @return int
     */
    protected function checkRoute($value)
    {
        return preg_match("/^[a-zA-Z][a-zA-Z0-9]*$/", $value);
    }
    
    /**
     * 判断PHP是否处于CLI模式下运行
     *
     * @return boolean
     */
    public function isCli()
    {
        return preg_match("/cli/i", PHP_SAPI) ? true : false;
    }
}

class Request
{
    
    /**
     * Request实例
     *
     * @var Request
     */
    protected static $_instance;

    /**
     * 基础地址
     *
     * @var string
     */
    protected $_baseUrl = null;

    /**
     * QUERY_STRING转化的数组
     *
     * @var array
     */
    protected $_queryStringArray;
    
    /**
     * 请求参数数组
     *
     * @var array
     */
    protected $_requestParams = [];
    
    /**
     * 存放请求的Header信息数组
     *
     * @var array
     */
    private $_headers = [];

    /**
     * Header Object
     *
     * @var Object
     */
    private $_header;

    /**
     * 预处理过的REQUEST_URI
     *
     * @var string
     */
    private $_requestUri;
    
    /**
     * CSRF令牌存储参数名
     *
     * @var string
     */
    private $_csrfParamName = '_mini-csrf-token';
    
    private $_csrfHeaderParamName = 'X-Mini-Csrf-Token';
    
    /**
     * 获取实例
     *
     * @return object
     */
    public static function getInstance()
    {
        if (self::$_instance === null) {
            self::$_instance = new self();
        }
        return self::$_instance;
    }
    
    /**
     * 构造
     */
    protected function __construct()
    {
        // Nothing ...
    }
    
    private function __clone()
    {}
    
    /**
     * 从$_SERVER['PHP_SELF']中提取基础地址
     *
     * @return string
     */
    public function setBaseUrl()
    {
        if ($this->_baseUrl === null) {
            $urlArray = explode('/', $_SERVER['PHP_SELF']);
            unset($urlArray[count($urlArray) - 1]);
            $this->_baseUrl = implode('/', $urlArray);
        }
        return $this->_baseUrl;
    }
    
    /**
     * 获取基础地址
     *
     * @return string
     */
    public function getBaseUrl()
    {
        if ($this->_baseUrl === null) {
            $this->_baseUrl = $this->setBaseUrl();
        }
        return $this->_baseUrl;
    }
    
    /**
     * 获取请求方法（旧名称，建议使用新名称 getMethod）
     * 
     * @return string
     */
    public function method()
    {
        return $this->getMethod();
    }
    
    /**
     * 获取请求方法（新名称）
     * 
     * @return string
     */
    public function getMethod()
    {
        if (isset($_SERVER['REQUEST_METHOD'])) {
            $method = $_SERVER['REQUEST_METHOD'];
        } elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
            $method = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'];
        } elseif (isset($_REQUEST['_method'])) {
            $method = $_REQUEST['_method'];
        } else {
            $method = 'GET';
        }
        
        return strtoupper($method);
    }

    /**
     * 获取QUERY_STRING数组
     *
     * @return array
     */
    public function getQueryStringArray()
    {
        if (isset($this->_queryStringArray)) {
            return $this->_queryStringArray;
        }
        
        $queryStringArray = [];
        parse_str($_SERVER['QUERY_STRING'], $queryStringArray);
        $this->_queryStringArray = $queryStringArray;
        
        return $queryStringArray;
    }
    
    /**
     * 解析请求参数
     *
     * @throws Exception
     * @return array
     */
    public function parseRequestParams($routeType)
    {
        $requestParams = [];
        
        if ($routeType == 'cli') {
            
            if (isset($_SERVER['argc']) && $_SERVER['argc'] > 2) {
                for ($i = 2; $i < $_SERVER['argc']; $i ++) {
                    if (strpos($_SERVER['argv'][$i], '=') > 0) {
                        $curParam = explode('=', $_SERVER['argv'][$i]);
                        $requestParams[$curParam[0]] = $curParam[1];
                    } else {
                        throw new Exception('Request params invalid.');
                    }
                }
            }
        } elseif ($routeType == 'rewrite') {
            
            $requestUri = $this->getRequestUri();
            
            if (! empty($_SERVER['QUERY_STRING'])) {
                $queryStringArray = $this->getQueryStringArray();
            }
            
            if ($requestUri != $this->_baseUrl) {
                $requestUri = str_replace($this->_baseUrl, '', $requestUri);
            }
            
            $uriArray = explode('/', $requestUri);
            
            $array = null;
            array_splice($uriArray, 0, 3);
            
            if (! empty($uriArray)) {
                foreach ($uriArray as $key => $value) {
                    if ($key % 2 == 0) {
                        $array[$value] = null;
                    } else {
                        $array[$uriArray[$key - 1]] = $value;
                    }
                }
                foreach ($array as $key => $value) {
                    if ($key != '' && $value !== null) {
                        $requestParams[$key] = $value;
                    }
                }
            }
            
            if (! empty($queryStringArray)) {
                $requestParams = array_merge($requestParams, $queryStringArray);
            }
        } elseif ($routeType == 'get') {
            
            if (! empty($_SERVER['QUERY_STRING'])) {
                $requestParams = $this->getQueryStringArray();
            }
        }
        
        return $requestParams;
    }
    
    /**
     * 获取请求Header信息数组
     *
     * @param string $name
     * @return multitype:
     */
    public function getHeaders($name = null)
    {
        if (empty($this->_headers)) {
            foreach ($_SERVER as $key => $val) {
                if ('HTTP_' == substr($key, 0, 5)) {
                    $this->_headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5)))))] = $val;
                }
            }
        }
        
        if (isset($name)) {
            if (isset($this->_headers[$name])) {
                return $this->_headers[$name];
            } else {
                return null;
            }
        }
        
        return $this->_headers;
    }

    /**
     * Get request header object
     *
     * @return object|\Mini\Header
     */
    public function getHeader()
    {
        if ($this->_header === null) {
            $this->_header = new Header();
            foreach ($_SERVER as $key => $val) {
                if ('HTTP_' == substr($key, 0, 5)) {
                    $this->_header->add(
                        str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5))))),
                        $val);
                }
            }
        }
        
        return $this->_header;
    }

    /**
     * 获取原始输入流
     *
     * @return string
     */
    public function getInputStream()
    {
        return file_get_contents('php://input');
    }

    /**
     * 获取预处理后的REQUEST_URI
     *
     * @return string
     */
    public function getRequestUri()
    {
        if ($this->_requestUri === null) {
            $requestUri = '';
            if (empty($_SERVER['QUERY_STRING'])) {
                $requestUri = $_SERVER['REQUEST_URI'];
            } else {
                $requestUri = str_replace('?' . $_SERVER['QUERY_STRING'], '', $_SERVER['REQUEST_URI']);
            }
            
            // 遇到常量定义的伪静态扩展名结尾时，转换可能存在的伪静态分隔符
            $match = [];
            if (preg_match('/\.(' . URL_SUFFIX . ')$/', $requestUri, $match)) {
                $requestUri = substr($requestUri, 0, - (strlen($match[1])+1));
                $pos = strrpos($requestUri, '/');
                $dir = substr($requestUri, 0, $pos);
                $file = substr($requestUri, $pos);
                $file = str_replace(URL_SPLIT_SYMBOL, '/', $file);
                $requestUri = $dir . $file;
            }
            
            $this->_requestUri = $requestUri;
        }
        
        return $this->_requestUri;
    }
    
    /**
     * 获取CSRF令牌存储键名
     *
     * @return string
     */
    public function getCsrfParamName()
    {
        return $this->_csrfParamName;
    }
    
    /**
     * 创建CSRF令牌
     *
     * @param string $type
     * @return string
     */
    public function createCsrfToken($type = 'cookie')
    {
        $token = getRandomString(64);
        
        Session::start();
        Session::set($this->_csrfParamName, $token);
        Session::commit();
        
        if ($type == 'cookie') {
            setcookie($this->_csrfParamName, $token, 0, '/', null, null, true);
        } elseif ($type == 'header') {
            $this->_http->header($this->_csrfHeaderParamName, $token);
        }
        
        
        return $token;
    }
    
    /**
     * 读取CSRF令牌
     *
     * @param string $type (cookie | session | header | get | post)
     * @return string
     */
    public function loadCsrfToken($type = 'cookie')
    {
        $token = '';
        
        if ($type == 'cookie') {
            if (isset($_COOKIE[$this->_csrfParamName])) {
                $token = $_COOKIE[$this->_csrfParamName];
            }
        } elseif ($type == 'session') {
            Session::start();
            $token = Session::get($this->_csrfParamName);
        } elseif ($type == 'header') {
            $token = $this->getHeaders($this->_csrfHeaderParamName);
            if (! is_string($token)) {
                $token = '';
            }
        } elseif ($type == 'get') {
            if (isset($_GET[$this->_csrfParamName])) {
                $token = $_GET[$this->_csrfParamName];
            }
        } elseif ($type == 'post') {
            if (isset($_POST[$this->_csrfParamName])) {
                $token = $_POST[$this->_csrfParamName];
            }
        }
        
        return $token;
    }
    
    /**
     * 校验客户端传入的CSRF令牌
     *
     * @throws Exception
     * @return boolean
     */
    public function checkCsrfToken()
    {
        $clientCsrfToken = $this->loadCsrfToken('cookie');
        
        if (! $clientCsrfToken) {
            $clientCsrfToken = $this->loadCsrfToken('header');
        }
        
        if (! $clientCsrfToken) {
            $clientCsrfToken = $this->loadCsrfToken('get');
        }
        
        if (! $clientCsrfToken) {
            $clientCsrfToken = $this->loadCsrfToken('post');
        }
        
        if (! $clientCsrfToken) {
            $this->_http->sendHttpStatus(403);
            echo 'Client CSRF-Token invalid.';
            die();
        }
        
        $serverCsrfToken = $this->loadCsrfToken('session');
        
        if (! $serverCsrfToken) {
            throw new Exception('Server CSRF-Token does not exist.');
        }
        
        if ($clientCsrfToken === $serverCsrfToken) {
            return true;
        }
        
        return false;
    }
}

class Response
{
    /**
     * Response 实例
     *
     * @var Response
     */
    protected static $_instance;
    
    /**
     * Header Object
     *
     * @var Object
     */
    private $_header;
    
    /**
     * Http Status Code
     *
     * @var integer
     */
    private $_httpStatusCode = 200;
    
    /**
     * Content-Type
     *
     * @var array
     */
    private $_contentType = [
        'html' => 'text/html',
        'text' => 'text/plain',
        'json' => 'application/json',
        'xml' => 'application/xml'
    ];
    
    /**
     * 输出方式
     *
     * @var string
     */
    private $_type = 'html';
    
    /**
     * Http status
     *
     * @var array
     */
    protected static $status = [
        // Informational 1xx
        100 => 'Continue',
        101 => 'Switching Protocols',
        
        // Success 2xx
        200 => 'OK',
        201 => 'Created',
        202 => 'Accepted',
        203 => 'Non-Authoritative Information',
        204 => 'No Content',
        205 => 'Reset Content',
        206 => 'Partial Content',
        
        // Redirection 3xx
        300 => 'Multiple Choices',
        301 => 'Moved Permanently',
        302 => 'Found', // 1.1
        303 => 'See Other',
        304 => 'Not Modified',
        305 => 'Use Proxy',
        // 306 is deprecated but reserved
        307 => 'Temporary Redirect',
        
        // Client Error 4xx
        400 => 'Bad Request',
        401 => 'Unauthorized',
        402 => 'Payment Required',
        403 => 'Forbidden',
        404 => 'Not Found',
        405 => 'Method Not Allowed',
        406 => 'Not Acceptable',
        407 => 'Proxy Authentication Required',
        408 => 'Request Timeout',
        409 => 'Conflict',
        410 => 'Gone',
        411 => 'Length Required',
        412 => 'Precondition Failed',
        413 => 'Request Entity Too Large',
        414 => 'Request-URI Too Long',
        415 => 'Unsupported Media Type',
        416 => 'Requested Range Not Satisfiable',
        417 => 'Expectation Failed',
        
        // Server Error 5xx
        500 => 'Internal Server Error',
        501 => 'Not Implemented',
        502 => 'Bad Gateway',
        503 => 'Service Unavailable',
        504 => 'Gateway Timeout',
        505 => 'HTTP Version Not Supported',
        509 => 'Bandwidth Limit Exceeded'
    ];
    
    /**
     * 获取 Response 单例
     *
     * @return \Mini\Response
     */
    public static function getInstance()
    {
        if (self::$_instance === null) {
            self::$_instance = new self();
        }
        
        return self::$_instance;
    }
    
    private function __clone()
    {}
    
    /**
     * Get response header object
     *
     * @return object|\Mini\Header
     */
    public function getHeader()
    {
        if ($this->_header === null) {
            $this->_header = new Header();
        }
        
        return $this->_header;
    }
    
    /**
     * Set response header data
     *
     * @param string $name
     * @param string $value
     * @return \Mini\Response
     */
    public function header($name, $value)
    {
        $this->getHeader()->set($name, $value);
        
        return $this;
    }
    
    /**
     * Set http status
     *
     * @param int $code
     * @return boolean
     */
    public function httpStatus($code)
    {
        if (isset(self::$status[$code])) {
            $this->_httpStatusCode = $code;
        } else {
            throw new Exception('Invalid http status code: ' . $code);
        }
        
        return $this;
    }
    
    /**
     * 获取 HTTP STATUS 信息
     *
     * @param int $code
     * @return string|boolean
     */
    public static function getStatusMsg($code)
    {
        if (isset(self::$status[$code])) {
            return self::$status[$code];
        }
        
        return false;
    }
    
    /**
     * Set Content-Type
     *
     * @param string $type html|text|json|xml
     * @return \Mini\Response
     */
    public function type($type)
    {
        if (isset($this->_contentType[$type])) {
            $this->_type = $type;
        } else {
            $this->_type = 'html';
        }
        
        return $this;
    }
    
    /**
     * 输出
     *
     * @param string $content
     */
    public function send($content = '')
    {
        if (SHOW_DEBUG === false) {
            ob_end_clean();
        }
        if (false === headers_sent()) {
            if (isset(self::$status[$this->_httpStatusCode])) {
                $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1';
                header($protocol . ' ' . $this->_httpStatusCode . ' ' . self::$status[$this->_httpStatusCode], true, $this->_httpStatusCode);
            } else {
                throw new Exception('Invalid http status code: ' . $this->_httpStatusCode);
            }
            $header = $this->getHeader();
            $headers = $header->getAll();
            foreach ($headers as $name => $value) {
                header($name . ': ' . $value);
            }
            if (! isset($headers['Content-Type'])) {
                header('Content-Type: ' . $this->_contentType[$this->_type]);
            }
            header('Cache-Control: ' . HTTP_CACHE_CONTROL);
        }
        
        echo $content;
        
        die();
    }
}

class Header
{
    
    /**
     * Header array
     *
     * @var array
     */
    private $_headers = [];
    
    /**
     * 添加一个 Header 如果 name 存在则返回 false
     *
     * @param string $name
     * @param string $value
     * @return boolean
     */
    public function add(string $name, string $value)
    {
        $name = $this->formatHeaderName($name);
        if (isset($this->_headers[$name])) {
            return false;
        } else {
            $this->_headers[$name] = $value;
        }
        
        return true;
    }
    
    /**
     * 设置一个 Header 如果 name 存在则覆盖旧的值
     *
     * @param string $name
     * @param string $value
     * @return boolean
     */
    public function set(string $name, string $value)
    {
        $name = $this->formatHeaderName($name);
        $this->_headers[$name] = $value;
        
        return true;
    }
    
    /**
     * 移除一个 Header 如果 name 不存在则返回 false
     *
     * @param string $name
     * @return boolean
     */
    public function remove(string $name)
    {
        $name = $this->formatHeaderName($name);
        if (! isset($this->_headers[$name])) {
            return false;
        } else {
            unset($this->_headers[$name]);
        }
        
        return true;
    }
    
    /**
     * 判断 Header 是否存在
     *
     * @param string $name
     * @return boolean
     */
    public function has(string $name)
    {
        $name = $this->formatHeaderName($name);
        
        return isset($this->_headers[$name]);
    }
    
    /**
     * 获取指定 name 的 Header 如果 name 不存在则返回 false
     *
     * @param string $name
     * @return boolean
     */
    public function get(string $name)
    {
        $name = $this->formatHeaderName($name);
        if (! isset($this->_headers[$name])) {
            return false;
        }
        
        return $this->_headers[$name];
    }
    
    /**
     * 获取全部 Header
     *
     * @return array
     */
    public function getAll()
    {
        return $this->_headers;
    }
    
    /**
     * 格式化 Header 名称
     *
     * @param string $name
     * @return string
     */
    public function formatHeaderName($name)
    {
        return str_replace(' ', '-', ucwords(strtolower(str_replace('-', ' ', $name))));
    }
}

class Http
{
    
    protected static $status = [
        // Informational 1xx
        100 => 'Continue',
        101 => 'Switching Protocols',
        
        // Success 2xx
        200 => 'OK',
        201 => 'Created',
        202 => 'Accepted',
        203 => 'Non-Authoritative Information',
        204 => 'No Content',
        205 => 'Reset Content',
        206 => 'Partial Content',
        
        // Redirection 3xx
        300 => 'Multiple Choices',
        301 => 'Moved Permanently',
        302 => 'Found', // 1.1
        303 => 'See Other',
        304 => 'Not Modified',
        305 => 'Use Proxy',
        // 306 is deprecated but reserved
        307 => 'Temporary Redirect',
        
        // Client Error 4xx
        400 => 'Bad Request',
        401 => 'Unauthorized',
        402 => 'Payment Required',
        403 => 'Forbidden',
        404 => 'Not Found',
        405 => 'Method Not Allowed',
        406 => 'Not Acceptable',
        407 => 'Proxy Authentication Required',
        408 => 'Request Timeout',
        409 => 'Conflict',
        410 => 'Gone',
        411 => 'Length Required',
        412 => 'Precondition Failed',
        413 => 'Request Entity Too Large',
        414 => 'Request-URI Too Long',
        415 => 'Unsupported Media Type',
        416 => 'Requested Range Not Satisfiable',
        417 => 'Expectation Failed',
        
        // Server Error 5xx
        500 => 'Internal Server Error',
        501 => 'Not Implemented',
        502 => 'Bad Gateway',
        503 => 'Service Unavailable',
        504 => 'Gateway Timeout',
        505 => 'HTTP Version Not Supported',
        509 => 'Bandwidth Limit Exceeded'
    ];
    
    /**
     * Http实例
     *
     * @var Http
     */
    protected static $_instance = [];
    
    /**
     * 用于输出的Header信息数组
     *
     * @var array
     */
    protected $_headers = [];
    
    /**
     * 获取实例
     *
     * @return object
     */
    public static function getInstance()
    {
        $name =  get_called_class();
        if (! isset(self::$_instance[$name]) || self::$_instance[$name] === null) {
            self::$_instance[$name] = new $name();
        }
        return self::$_instance[$name];
    }
    
    /**
     * 构造
     */
    final protected function __construct()
    {}
    
    /**
     * 克隆
     */
    private function __clone()
    {}
    
    /**
     * 写入头信息
     *
     * @param string $name
     * @param string $value
     * @return \Mini\Http
     */
    public function header($name, $value = null)
    {
        $this->_headers[$name] = $value;
        
        return $this;
    }
    
    /**
     * 输出
     *
     * @param int $code
     * @param string $content
     * @throws \Exception
     */
    public function response($code, $content)
    {
        if (! isset(self::$status[$code])) {
            throw new Exception('Invalid http status code: ' . $code);
        }
        
        if (SHOW_DEBUG === false) {
            ob_end_clean();
        }
        
        if (false === headers_sent()) {
            
            $this->sendHttpStatus($code);
            
            foreach ($this->_headers as $name => $value) {
                $name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $name)));
                header($name . ': ' . $value);
            }
        }
        
        if (! isset($this->_headers['Content-Type'])) {
            header("Content-Type: text/html; charset=utf-8");
        }
        header('Cache-Control: ' . HTTP_CACHE_CONTROL);
        
        echo $content;
        
        die();
    }
    
    /**
     * 发送HTTP状态
     *
     * @param int $code
     * @return boolean
     */
    public static function sendHttpStatus($code)
    {
        $flag = false;
        if (isset(self::$status[$code])) {
            $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1';
            header($protocol . ' ' . $code . ' ' . self::$status[$code], true, $code);
            $flag = true;
        }
        
        return $flag;
    }
    
    /**
     * 判断HTTP状态是否存在
     *
     * @param int $code
     * @return multitype:string |boolean
     */
    public static function isStatus($code)
    {
        if (isset(self::$status[$code])) {
            return self::$status[$code];
        }
        
        return false;
    }
}

class Exception extends \Exception
{
    
    /**
     * 构造
     *
     * @param string $message 错误信息
     * @param int $code 错误代码
     */
    public function __construct($message, $code = 0, $level = Log::ERROR, $position = null)
    {
        Log::record($message, $level, $position);
        parent::__construct($message, $code);
    }
    
    /**
     * 重构 toString
     */
    public function __toString()
    {
        if (SHOW_ERROR === true) {
            return parent::__toString();
        } else {
            self::showErrorPage($this->code);
        }
    }
    
    /**
     * 显示自定义的报错内容
     *
     * @param int $code
     */
    public static function showErrorPage($code)
    {
        $response = Response::getInstance();
        $status = $response->getStatusMsg($code);
        if ($status === false) {
            $code = 500;
            $response->getStatusMsg($code);
        }
        $info = '<html><head><title>Error</title></head><body><h1>An error occurred</h1>';
        $info .= '<h2>' . $code . ' ' . $status . '</h2></body></html>';
        $response->header('Content-Type', 'text/html; charset=utf-8')->httpStatus($code)->send($info);
        
        die();
    }
}

class Action
{
    
    /**
     * View实例
     *
     * @var View
     */
    protected $view;
    
    /**
     * Params实例
     *
     * @var Params
     */
    protected $params;
    
    /**
     * Request实例
     *
     * @var Request
     */
    protected $_request;
    
    /**
     * 构造
     *
     * @param string $controller
     * @param string $action
     * @return Action
     */
    function __construct()
    {
        $this->view = new View();
        $this->params = Params::getInstance();
        $this->_request = Request::getInstance();
        
        if (method_exists($this, '_init')) {
            $this->_init();
        }
    }
    
    /**
     * 向View传入变量
     *
     * @param mixed $variable
     * @param mixed $value
     */
    final protected function assign($variable, $value)
    {
        $this->view->assign($variable, $value);
    }
    
    /**
     * 转至给定的控制器和动作（旧命名）
     *
     * @param string $action
     * @param string $controller
     */
    final protected function _forward($action, $controller = null)
    {
        $this->_forward($action, $controller);
    }

    /**
     * 转至给定的控制器和动作
     *
     * @param string $action
     * @param string $controller
     */
    final protected function forward($action, $controller = null)
    {
        $app = App::getInstance();
        if ($action == $app->action) {
            if ($controller === null || $controller == $app->controller) {
                return false;
            }
        }
        if ($controller !== null) {
            $app->setController($controller);
        }
        $app->setAction($action)->dispatch();
        die();
    }
    
    /**
     * 显性跳转
     *
     * @param string $action
     * @param string $controller
     */
    final protected function redirect(string $action, string $controller = null)
    {
        $baseUrl = $this->request->getBaseUrl();
        $redirectUrl = $baseUrl;
        if ($controller !== null) {
            $redirectUrl .= '/' . $controller;
        } else {
            $redirectUrl .= '/' . App::getInstance()->controller;
        }
        $redirectUrl .= '/' . $action;
        header('Location:' . $redirectUrl);
        die();
    }
}

class View
{
    
    /**
     * 控制器
     *
     * @var string
     */
    private $_controller;
    
    /**
     * 动作
     *
     * @var string
     */
    private $_action;
    
    /**
     * Request实例
     *
     * @var Request
     */
    private $_request;
    
    /**
     * 基础路径
     *
     * @var string
     */
    protected $_baseUrl;
    
    /**
     * Layout实例
     *
     * @var Layout
     */
    public $_layout;
    
    /**
     * 代码块数组
     *
     * @var array
     */
    private $_blockData = [];
    
    /**
     * 代码块开启状态
     *
     * @var int
     */
    private $_blockStatus = 0;
    
    /**
     * JS文件数组
     *
     * @var array
     */
    private $_jsFile = [];
    
    /**
     * 构造
     */
    function __construct()
    {
        $this->_request = Request::getInstance();
        $app = App::getInstance();
        $this->_controller = $app->controller;
        $this->_action = $app->action;
        
        if (LAYOUT_ON === true) {
            $this->_layout = Layout::getInstance();
            $this->_layout->setLayoutPath(LAYOUT_PATH);
        }
    }
    
    /**
     * 获取基础路径
     */
    public function baseUrl()
    {
        if ($this->_baseUrl === null) {
            $this->_baseUrl = $this->_request->getBaseUrl();
        }
        return $this->_baseUrl;
    }
    
    public function __set($variable, $value)
    {
        $this->assign($variable, $value);
    }
    
    /**
     * 接收来自于控制器的变量
     *
     * @param string $variable
     * @param mixed $value
     */
    public function assign($variable, $value)
    {
        if (substr($variable, 0, 1) != '_') {
            $this->$variable = $value;
            return true;
        }
        return false;
    }
    
    /**
     * 显示
     */
    final public function display()
    {
        $view = APP_PATH . DS . 'View' . DS;
        $view .= strtolower($this->_controller) . DS . $this->_action . '.php';
        if (! file_exists($view)) {
            throw new Exception('View "' . $this->_action . '" does not exist.', 404);
        }
        $content = $this->render($view);
        $response = Response::getInstance();
        if (LAYOUT_ON === true && $this->_layout->getLayout()) {
            $this->_layout->content = $content;
            $layoutScript = $this->_layout->getLayoutScript();
            $finalViewPage = $this->render($layoutScript);
            if (! empty($this->_jsFile)) {
                foreach ($this->_jsFile as $url) {
                    $js = '<script src="' . $url . '"></script>';
                    $finalViewPage = str_replace('</body>', $js . "\n</body>", $finalViewPage);
                }
            }
            $response->httpStatus(200)->send($finalViewPage);
        } else {
            $response->httpStatus(200)->send($content);
        }
        
        die();
    }
    
    /**
     * 渲染器
     *
     * @param string $script
     * @param boolean $check
     * @throws Exception
     * @return string
     */
    final public function render($script, $check = true)
    {
        if ($check === true) {
            if (! file_exists($script)) {
                throw new Exception('File "' . $script . '" does not exist.', 404);
            }
        }
        
        if (SHOW_DEBUG === false) {
            ob_end_clean();
        }
        
        ob_start();
        include ($script);
        $content = ob_get_contents();
        ob_end_clean();
        ob_start();
        
        return $content;
    }
    
    /**
     * 开启一个代码块
     *
     * @param string $blockName
     * @return boolean
     */
    public function beginBlock($blockName)
    {
        if ($this->_blockStatus == 1) {
            return false;
        }
        $this->_blockStatus = 1;
        $this->_blockData[$blockName] = '';
        ob_start();
        ob_implicit_flush(false);
        
        return true;
    }
    
    /**
     * 结束当前的代码块
     *
     * @return boolean
     */
    public function endBlock()
    {
        if ($this->_blockStatus == 0) {
            return false;
        }
        $block = ob_get_clean();
        end($this->_blockData);
        $lastKey = key($this->_blockData);
        if ($lastKey == null) {
            return false;
        }
        $this->_blockData[$lastKey] = $block;
        $this->_blockStatus = 0;
        
        return true;
    }
    
    /**
     * 插入代码块
     *
     * @param string $blockName
     * @return boolean
     */
    public function insertBlock($blockName)
    {
        if (! isset($this->_blockData[$blockName])) {
            return false;
        }
        echo $this->_blockData[$blockName];
        
        return true;
    }
    
    /**
     * 设置JS文件资源的引入
     *
     * @param string $jsFileUrl
     * @return boolean
     */
    public function setJsFile($jsFileUrl)
    {
        if (!isset($jsFileUrl) || empty($jsFileUrl)) {
            return false;
        }
        $this->_jsFile[] = $jsFileUrl;
        
        return true;
    }
}

class Layout
{
    
    /**
     * 布局变量容器
     *
     * @var array
     */
    protected $_container;
    
    /**
     * 布局文件路径
     *
     * @var mixed
     */
    private $_layoutPath;
    
    /**
     * 布局名称
     *
     * @var string
     */
    protected $_layout;
    
    /**
     * Layout Instance
     *
     * @var Layout
     */
    protected static $_instance;
    
    /**
     * 获取实例
     */
    public static function getInstance()
    {
        if (self::$_instance === null) {
            self::$_instance = new self();
        }
        return self::$_instance;
    }
    
    /**
     * 构造
     */
    final protected function __construct()
    {}
    
    /**
     * 克隆
     */
    private function __clone()
    {}
    
    public function __set($key, $value)
    {
        $this->_container[$key] = $value;
    }
    
    public function __get($key)
    {
        if (isset($this->_container[$key])) {
            return $this->_container[$key];
        }
        
        return null;
    }
    
    /**
     * 设置布局文件所在路径
     *
     * @param string $path 布局文件存放路径
     */
    public function setLayoutPath($path)
    {
        $this->_layoutPath = (string) $path;
        
        return $this;
    }
    
    /**
     * 获取布局文件所在路径
     */
    public function getLayoutPath()
    {
        return $this->_layoutPath;
    }
    
    /**
     * 设置布局
     *
     * @param string $name 布局名称
     */
    public function setLayout($name)
    {
        if (! preg_match('/^[a-zA-Z][a-zA-Z0-9_]*$/', $name)) {
            throw new Exception('Layout "' . $name . '"  invalid.');
        }
        
        $this->_layout = (string) $name;
        
        return $this;
    }
    
    /**
     * 获取布局
     *
     * @param string $name
     */
    public function getLayout()
    {
        return $this->_layout;
    }
    
    /**
     * 获取布局脚本
     *
     * @param string $layoutScript
     */
    public function getLayoutScript()
    {
        $layoutScript = $this->getLayoutPath() . DS . $this->getLayout() . '.php';
        if (! file_exists($layoutScript)) {
            throw new Exception('Layout "' . $this->getLayout() . '" does not exist.');
        }
        
        return $layoutScript;
    }
}

class Config
{
    
    /**
     * Config Instance
     *
     * @var Config
     */
    protected static $_instance;
    
    private $_confData = [];
    
    /**
     * 获取实例
     */
    public static function getInstance()
    {
        if (self::$_instance === null) {
            self::$_instance = new self();
        }
        return self::$_instance;
    }
    
    /**
     * 构造
     */
    final protected function __construct()
    {}
    
    /**
     * 克隆
     */
    private function __clone()
    {}
    
    /**
     * 读取配置
     *
     * @param string $config
     */
    public function load($config)
    {
        $lastPos = strpos($config, ':');
        if ($lastPos !== false) {
            $confName = strstr($config, ':', true);
            $confKey = substr($config, $lastPos + 1);
        } else {
            $confName = $config;
        }
        
        if (! isset($this->_confData[$confName])) {
            
            if (APP_ENV == 'prod') {
                $confFile = CONFIG_PATH . DS . $confName . '.php';
            } else {
                $confFile = CONFIG_PATH . DS . $confName . '-' . APP_ENV . '.php';
            }
            
            if (file_exists($confFile)) {
                include ($confFile);
            } else {
                throw new Exception('Config "' . $confName . '" not found.');
            }
            
            if (isset(${$confName})) {
                $this->_confData[$confName] = ${$confName};
            } else {
                return null;
            }
        }
        
        if (isset($confKey) && isset($this->_confData[$confName][$confKey])) {
            return $this->_confData[$confName][$confKey];
        }
        
        return $this->_confData[$confName];
    }
}

class Session
{
    
    /**
     * 开启会话
     *
     * @param array $params
     */
    public static function start($params = [])
    {
        $flag = false;
        if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
            if (session_status() != 2) {
                $flag = true;
            }
        } else {
            if (! isset($_SESSION)) {
                $flag = true;
            }
        }
        
        if ($flag === true) {
            if (! is_array($params)) {
                throw new Exception('The session params must be an array.');
            }
            foreach ($params as $paramName => $paramValue) {
                ini_set('session.' . $paramName, $paramValue);
            }
            
            // 开启会话
            session_start();
        }
        
        return true;
    }
    
    /**
     * 读取或设置会话ID
     *
     * @param string $id
     * @return string
     */
    public static function id($id = null)
    {
        return isset($id) ? session_id($id) : session_id();
    }
    
    /**
     * 读取会话数据
     *
     * @param string $name
     * @return mixed|NULL
     */
    public static function get($name = null)
    {
        if (isset($name) && $name != null) {
            if (! is_string($name)) {
                throw new Exception('The session name must be a string.');
            }
            
            if (isset($_SESSION[$name])) {
                return $_SESSION[$name];
            }
        } else {
            return $_SESSION;
        }
        
        return null;
    }
    
    /**
     * 写入会话数据
     *
     * @param string $name
     * @param mixed $value
     * @throws Exception
     * @return boolean
     */
    public static function set($name, $value)
    {
        if (! is_string($name)) {
            throw new Exception('The session name must be a string.');
        }
        
        if ($value == null) {
            unset($_SESSION[$name]);
        } else {
            $_SESSION[$name] = $value;
        }
        
        return true;
    }
    
    /**
     * 检查会话数据是否存在（旧方法名，建议使用has方法）
     *
     * @param string $name
     * @throws Exception
     * @return boolean
     */
    public static function is_set($name)
    {
        return self::has($name);
    }

    /**
     * 检查会话数据是否存在
     *
     * @param string $name
     * @throws Exception
     * @return boolean
     */
    public static function has($name)
    {
        if (! is_string($name)) {
            throw new Exception('The session name must be a string.');
        }
        
        return isset($_SESSION[$name]);
    }

    /**
     * 销毁会话
     *
     * @return boolean
     */
    public static function destroy()
    {
        if (isset($_SESSION)) {
            $_SESSION = [];
            if (ini_get('session.use_cookies')) {
                $p = session_get_cookie_params();
                setcookie(session_name(), '', time() - 86400,
                    $p['path'], $p['domain'], $p['secure'], $p['httponly']
                    );
            }
        }
        
        return session_destroy();
    }
    
    /**
     * 写入会话数据并关闭会话连接
     *
     * @return boolean
     */
    public static function commit()
    {
        return session_commit();
    }
    
    /**
     * 获取会话状态
     *
     * @return int
     */
    public static function status()
    {
        if (version_compare(PHP_VERSION, '5.4.0', '<')) {
            throw new Exception('Not support session_status().');
        }
        
        return session_status();
    }
}

use PDO;

class Db
{
    /**
     * 数据库连接
     *
     * @var object | resource | null
     */
    protected $_dbh = null;
    
    /**
     * 数据库连接参数
     *
     * @var array
     */
    protected $_params = [];
    
    /**
     * 最后一次执行的SQL语句
     *
     * @var string
     */
    protected $_lastSql = null;
    
    /**
     * 调试
     *
     * @var boolean
     */
    protected $_debug = false;
    
    /**
     * 构造
     *
     * @param array $params
     *            => array (
     *            host          => (string) 主机，默认值为localhost
     *            dbname        => (string) 数据库名
     *            username      => (string) 用户名
     *            passwd        => (string) 密码
     *
     *            port          => (string) 端口
     *            charset       => (string) 字符集编码，默认值为utf8
     *            persistent    => (boolean) 是否启用持久连接，默认值为false
     *            )
     * @return Db
     */
    public function __construct($params)
    {
        if (! is_array($params)) {
            throw new Exception('Adapter params must be in an array.');
        }
        
        if (! isset($params['host'])) {
            throw new Exception('Database host is not defined.');
        } elseif (! isset($params['port'])) {
            throw new Exception('Database port is not defined.');
        } elseif (! isset($params['username'])) {
            throw new Exception('Database username is not defined.');
        } elseif (! isset($params['passwd'])) {
            throw new Exception('Database passwd is not defined.');
        } elseif (! isset($params['dbname'])) {
            throw new Exception('Database dbname is not defined.');
        }
        
        if (! isset($params['charset'])) {
            $params['charset'] = 'utf8';
        }
        
        if (! isset($params['persistent'])) {
            $params['persistent'] = false;
        }
        
        $this->_params = $params;
    }
    
    /**
     * 创建一个数据源
     */
    private function _dsn()
    {
        $dsn = [];
        
        if (isset($this->_params['host']) && is_string($this->_params['host'])) {
            $dsn['host'] = $this->_params['host'];
        } else {
            $dsn['host'] = 'localhost';
        }
        
        if (isset($this->_params['port'])) {
            $dsn['port'] = $this->_params['port'];
        }
        
        if (isset($this->_params['dbname']) && is_string($this->_params['dbname'])) {
            $dsn['dbname'] = $this->_params['dbname'];
        } else {
            throw new Exception('"dbname" must be in the params of Db.');
        }
        
        foreach ($dsn as $key => $val) {
            $dsn[$key] = "$key=$val";
        }
        
        return 'mysql:' . implode(';', $dsn);
    }
    
    /**
     * 创建一个数据库连接
     */
    protected function _connect()
    {
        if ($this->_dbh)
            return;
            
            if (! class_exists('PDO')) {
                throw new Exception('Not support PDO.');
            }
            
            $dsn = $this->_dsn();
            
            $this->_params['options'][PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION;
            
            if (version_compare(PHP_VERSION, '5.3.6', '>=')) {
                $dsn .= ';charset=' . $this->_params['charset'];
            } else {
                if (defined('PDO::MYSQL_ATTR_INIT_COMMAND')) {
                    $this->_params['options'][PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES ' . $this->_params['charset'];
                }
            }
            
            if (isset($this->_params['persistent']) && ($this->_params['persistent'] == true)) {
                $this->_params['options'][PDO::ATTR_PERSISTENT] = true;
            } else {
                $this->_params['options'][PDO::ATTR_PERSISTENT] = false;
            }
            
            try {
                $this->_dbh = new PDO($dsn, $this->_params['username'], $this->_params['passwd'], $this->_params['options']);
            } catch (Exception $e) {
                throw new Exception('Database connection failed.');
            }
            
            if (version_compare(PHP_VERSION, '5.3.6', '<') && ! defined('PDO::MYSQL_ATTR_INIT_COMMAND')) {
                $this->_dbh->exec('SET NAMES ' . $this->_params['charset']);
            }
    }
    
    /**
     * 执行SQL语句
     *
     * @param string $sql
     * @return int
     */
    public function execSql($sql = null)
    {
        $this->_connect();
        $this->_setLastSql($sql);
        if ($this->_debug === true)
            $this->_debugSql($sql);
            try {
                $affected = $this->_dbh->exec($sql);
                if ($affected === false) {
                    $this->_getPdoError();
                }
                return $affected;
            } catch (\PDOException $e) {
                throw new Exception($e);
            }
    }
    
    /**
     * 查询SQL语句
     *
     * @param string $sql SQL语句
     * @param string $queryMode 查询方式(All or Row)
     * @return array
     */
    public function query($sql = null, $queryMode = 'All')
    {
        $this->_connect();
        $this->_setLastSql($sql);
        if ($this->_debug === true)
            $this->_debugSql($sql);
            try {
                $recordset = $this->_dbh->query($sql);
                if ($recordset === false) {
                    $this->_getPdoError();
                }
                $recordset->setFetchMode(PDO::FETCH_ASSOC);
                
                if ($queryMode == 'All') {
                    $result = $recordset->fetchAll();
                } elseif ($queryMode == 'Row') {
                    $result = $recordset->fetch();
                } else {
                    $result = null;
                }
                
                return $result;
            } catch (\PDOException $e) {
                throw new Exception($e);
            }
    }
    
    /**
     * 插入记录
     *
     * @param string $table 表名
     * @param array $data 数据 array(col => value)
     * @param boolean $prepare 是否进行预处理
     * @return int
     */
    public function insert($table, array $data, $prepare = true)
    {
        if ($prepare === true) {
            $result = $this->prepareInsert($table, $data);
        } else {
            $result = $this->execSql("INSERT INTO `$table` (`" . implode('`,`', array_keys($data)) . "`) VALUES ('" . implode("','", $data) . "')");;
        }
        
        return $result;
    }
    
    /**
     * 预处理方式插入记录
     *
     * @param string $table 表名
     * @param array $data 数据 array(col => value)
     * @return boolean
     */
    public function prepareInsert($table, array $data)
    {
        $this->_connect();
        if (empty($data)) {
            return false;
        }
        $prepareParams = [];
        foreach ($data as $k => $v) {
            if (is_array($v)) {
                throw new Exception('Value cannot be an array.');
                return false;
            }
            $prepareParams[':' . $k] = $v;
        }
        try {
            $sql = "INSERT INTO `$table` (`" . implode('`, `', array_keys($data)) . "`) VALUES (" . implode(', ', array_keys($prepareParams)) . ")";
            $stmt = $this->_dbh->prepare($sql);
            $res = $stmt->execute($prepareParams);
            return $res;
        } catch (\PDOException $e) {
            throw new Exception($e);
        }
        
        return false;
    }
    
    /**
     * 批量插入记录
     *
     * @param string $table
     * @param array $dataArray = array(
     *        0 => array(col1 => value1, col2 => value2),
     *        1 => array(col1 => value1, col2 => value2),
     *        ...
     *        )
     * @param boolean $prepare 是否进行预处理
     * @return int
     */
    public function insertAll($table, array $dataArray, $prepare = true)
    {
        if ($prepare === true) {
            $result = $this->prepareInsertAll($table, $dataArray);
        } else {
            $sql = "INSERT INTO `$table` (`" . implode('`,`', array_keys($dataArray[0])) . "`) VALUES ";
            $valSqls = [];
            foreach ($dataArray as $data) {
                $valSqls[] = "('" . implode("','", $data) . "')";
            }
            $sql .= implode(', ', $valSqls);
            $result = $this->execSql($sql);
        }
        
        return $result;
    }
    
    /**
     * 预处理方式批量插入记录
     *
     * @param string $table
     * @param array $dataArray = array(
     *        0 => array(col1 => value1, col2 => value2),
     *        1 => array(col1 => value1, col2 => value2),
     *        ...
     *        )
     * @return boolean
     */
    public function prepareInsertAll($table, array $dataArray)
    {
        $this->_connect();
        if (empty($dataArray)) {
            return false;
        }
        $prepareParams = [];
        $prepareData = [];
        foreach ($dataArray as $key => $data) {
            if (empty($data)) {
                throw new Exception('Incorrect data format.');
                return false;
            }
            if (! is_array($data)) {
                throw new Exception('Incorrect data format.');
                return false;
            }
            foreach ($data as $k => $v) {
                if (is_array($v)) {
                    throw new Exception('Value cannot be an array.');
                    return false;
                }
                $prepareParams[$key][] = ':' . $k . $key;
                $prepareData[':' . $k . $key] = $v;
            }
        }
        try {
            $sql = "INSERT INTO `$table` (`" . implode('`, `', array_keys($dataArray[0])) . "`) VALUES ";
            $valSqls = [];
            foreach ($prepareParams as $curParams) {
                $valSqls[] = "(" . implode(', ', $curParams) . ")";
            }
            $sql .= implode(', ', $valSqls);
            $stmt = $this->_dbh->prepare($sql);
            $res = $stmt->execute($prepareData);
            return $res;
        } catch (\PDOException $e) {
            throw new Exception($e);
        }
        
        return false;
    }
    
    /**
     * 更新记录
     *
     * @param string $table 表名
     * @param array $data 数据 array(col => value)
     * @param string $where 条件
     * @return int
     */
    public function update($table, array $data, $where = '')
    {
        $sql = '';
        if (! empty($data)) {
            foreach ($data as $key => $value) {
                $sql .= ", `$key`='$value'";
            }
        }
        $sql = substr($sql, 1);
        $sql = "UPDATE `$table` SET $sql" . (($where) ? " WHERE $where" : '');
        
        return $this->execSql($sql);
    }
    
    /**
     * 替换记录
     *
     * @param string $table 表名
     * @param array $data 数据 array(col => value)
     * @return int
     */
    public function replace($table, array $data)
    {
        $sql = "REPLACE INTO `$table`(`" . implode('`,`', array_keys($data)) . "`) VALUES ('" . implode("','", $data) . "')";
        
        return $this->execSql($sql);
    }
    
    /**
     * 删除记录
     *
     * @param string $table 表名
     * @param string $where 条件
     * @return int
     */
    public function delete($table, $where = '')
    {
        $sql = "DELETE FROM `$table`" . (($where) ? " WHERE $where" : '');
        return $this->execSql($sql);
    }
    
    /**
     * 按指定条件查询行数
     *
     * @param string $table
     * @param string $col
     * @param string $where
     * @return int
     */
    public function countRow($table, $col = '*', $where = '')
    {
        $sql = "SELECT COUNT($col) AS rows FROM `$table`" . (($where) ? " WHERE $where" : '');
        $result = $this->query($sql, 'Row');
        return $result['rows'];
    }
    
    /**
     * 获取字段最大值
     *
     * @param string $table 表名
     * @param string $col 字段名
     * @param string $where 条件
     */
    public function getMaxValue($table, $col, $where = '')
    {
        $sql = "SELECT MAX($col) AS max_value FROM `$table`" . (($where) ? " WHERE $where" : '');
        $result = $this->query($sql, 'Row');
        $maxValue = $result["max_value"];
        if ($maxValue == "" || $maxValue == null) {
            $maxValue = 0;
        }
        return $maxValue;
    }
    
    /**
     * 检查数据表是否存在
     *
     * @param string $table
     * @param string $dbname
     * @return boolean
     */
    public function checkTableIsExist($table, $dbname = null)
    {
        if ($dbname === null) {
            $dbname = $this->_params['dbname'];
        }
        $sql = "SELECT * FROM information_schema.TABLES WHERE TABLE_SCHEMA='" . $dbname . "' AND TABLE_NAME = '" . $table . "'";
        $result = $this->query($sql, 'Row');
        if (! empty($result)) {
            if ($result['TABLE_NAME'] == $table) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * 获取表引擎
     *
     * @param string $table 表名
     * @return string
     */
    public function getTableEngine($table)
    {
        $sql = "SHOW TABLE STATUS FROM `" . $this->_params['dbname'] . "` WHERE `Name`='" . $table . "'";
        $result = $this->query($sql);
        return $result[0]['Engine'];
    }
    
    /**
     * 事务开始
     */
    protected function _beginTransaction()
    {
        $this->_connect();
        $this->_dbh->setAttribute(PDO::ATTR_AUTOCOMMIT, false);
        $this->_dbh->beginTransaction();
    }
    
    /**
     * 事务提交
     */
    protected function _commit()
    {
        $this->_connect();
        $this->_dbh->commit();
        $this->_dbh->setAttribute(PDO::ATTR_AUTOCOMMIT, 1);
    }
    
    /**
     * 事务回滚
     */
    protected function _rollBack()
    {
        $this->_connect();
        $this->_dbh->rollBack();
        $this->_dbh->setAttribute(PDO::ATTR_AUTOCOMMIT, 1);
    }
    
    /**
     * 通过事务处理多条SQL语句
     *
     * @param array $arraySql
     * @return boolean
     */
    public function execTrans(array $arraySql)
    {
        try {
            $this->_beginTransaction();
            foreach ($arraySql as $sql) {
                $this->execSql($sql);
            }
            $this->_commit();
            return true;
        } catch (\PDOException $e) {
            $this->_rollBack();
            throw new Exception($e);
        }
    }
    
    /**
     * 获得最后一次插入记录的自增主键
     */
    public function lastInsertId()
    {
        $this->_connect();
        return $this->_dbh->lastInsertId();
    }
    
    /**
     * 捕获PDO错误信息
     */
    private function _getPdoError()
    {
        $this->_connect();
        if ($this->_dbh->errorCode() != '00000') {
            $errorInfo = $this->_dbh->errorInfo();
            throw new Exception($errorInfo[2]);
        }
    }
    
    /**
     * 关闭数据库连接
     */
    public function close()
    {
        $this->_dbh = null;
    }
    
    /**
     * 获取最后一次执行的SQL语句
     */
    public function getLastSql()
    {
        return $this->_lastSql;
    }
    
    /**
     * 保存最后一次执行的SQL语句
     *
     * @param string $sql
     */
    protected function _setLastSql($sql = null)
    {
        $this->_lastSql = $sql;
    }
    
    /**
     * 开启调试模式
     */
    public function debug()
    {
        $this->_debug = true;
        return $this;
    }
    
    /**
     * 输出调试信息
     *
     * @param string $sql
     */
    protected function _debugSql($sql)
    {
        echo "<p>----------DEBUG SQL BEGIN----------</p>\n";
        echo "<p><pre>$sql</pre></p>\n";
        echo "<p>----------DEBUG SQL END----------</p>\n";
        die();
    }
}
